// 版权归 瞄你个汪 所有。使用本代码应遵守相关法律法规和Apache 2.0开源许可要求。
package top.geeke.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.jwt.JWTPayload;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.geeke.annotation.AllowAnonymous;
import top.geeke.config.AppConfiguration;
import top.geeke.config.SysUserSessionManager;
import top.geeke.config.cache.RedisCache;
import top.geeke.constants.AppCachePool;
import top.geeke.constants.AppConfigPool;
import top.geeke.controller.dto.*;
import top.geeke.entity.SysUser;
import top.geeke.eunm.SysErrorEnum;
import top.geeke.eunm.SysStatusEnum;
import top.geeke.service.ISysConfigService;
import top.geeke.service.ISysMenuService;
import top.geeke.service.ISysUserService;
import top.geeke.toolkit.*;
import top.geeke.toolkit.http.AppException;
import top.geeke.toolkit.http.Result;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;


/**
 * <p>
 *  系统授权控制层
 * </p>
 * @author 瞄你个汪
 * @since 2024/6/4 13:07
 */
@Api(tags = "⚙ 系统授权控制")
@RestController()
@RequestMapping("/api/sysAuth")
public class SysAuthController {
    @Autowired
    private ISysUserService service;
    @Autowired
    private ISysConfigService configService;
    @Autowired
    private ISysMenuService sysMenuService;
    @Autowired
    private CaptchaManager captchaManager;
    @Autowired
    private AppConfiguration appConfiguration;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    SysUserSessionManager sessionManager;

    /**
     * <p>
     *  用户登录
     * </p>
     * @param input 分页参数
     * @return 查询结果
     */
    @AllowAnonymous
    @PostMapping("login")
    @ApiOperation(value = "用户登录", notes = "用户登录")
    public Result<UserLoginOutput> login(@Validated @RequestBody UserLoginInput input, HttpServletRequest request) {
        String account = input.getAccount();

        // 获取最大认证失败次数
        int maxAuthFailures = appConfiguration.getCryptogram().getMaxAuthFailures();

        // 校验域编码登录参数
        Boolean isDomainLogin = configService.getValue(Boolean.class, AppConfigPool.Sys_Domain_Login);
        String host = isDomainLogin ? request.getServerName() : input.getDomainCode();
        if (Strings.isEmpty(host)) throw new AppException("域编码不能为空");

        // 获取缓存键名称
        final String CACHE_KEY = String.format(AppCachePool.SYSTEM_USER_PASSWD_ERROR_COUNT, isDomainLogin ? host : host, account);

        // 检查密码错误次数，超过则返回冻结时长
        Long count = redisCache.get(CACHE_KEY, Long.class);
        if (count != null && count >= maxAuthFailures) {
            Long seconds = redisCache.getExpire(CACHE_KEY);
            throw new AppException(SysErrorEnum.R4004, TimeFormatter.formatSeconds(seconds));
        }

        // 校验验证码
        Boolean enableCaptcha = configService.getValue(Boolean.class, AppConfigPool.Sys_Captcha);
        if (enableCaptcha) {
            String code = input.getCode();
            String codeId = input.getCodeId();
            if (Strings.isEmpty(code) || Strings.isEmpty(codeId)) throw new AppException("验证码不能为空");
            if (!captchaManager.verify(codeId, code)) throw new AppException("验证码有误");
        }

        // 查找用户，并校验密码
        SysUser user = isDomainLogin ? service.getByAccountAndHost(account, host) : service.getByAccountAndDomainCode(account, host);
        if (ObjectUtils.isEmpty(user) || !SysUserTool.validationCipherPassword(input.getPassword(), user.getPassword())) {
            // 记录登录失败次数，过期时长为30分钟
            long seconds = 30 * 60L;
            redisCache.expire(CACHE_KEY, seconds);
            count = redisCache.increment(CACHE_KEY, 1L);
            if (count >= maxAuthFailures) {
                // TODO 异步向账号发送告警信息
                throw new AppException(SysErrorEnum.R4004, TimeFormatter.formatSeconds(seconds));
            }
            throw new AppException(SysErrorEnum.R4003, maxAuthFailures - count);
        }

        // 登录成功清空密码错误次数
        redisCache.remove(CACHE_KEY);
        return Result.ok(createToken(user));
    }

    /**
     * 创建Token
     */
    private UserLoginOutput createToken(SysUser user) {
        if (user.getStatus() != SysStatusEnum.Enabled) throw new AppException(SysErrorEnum.R4005);
        if (user.isExpired()) throw new AppException(SysErrorEnum.R4006);

        // TODO 单设备登录

        Map<String,Object> payload = new HashMap<String,Object>() {{
            put(JWTPayload.JWT_ID, user.getTenantId() + "-" + user.getId());
        }};

        Long tokenExpire = configService.getValue(Long.class, AppConfigPool.Sys_Token_Expire);
        String accessToken = jwtUtils.encrypt(payload, tokenExpire);

        Long refreshTokenExpire = configService.getValue(Long.class, AppConfigPool.Sys_Refresh_Token_Expire);
        String refreshToken = jwtUtils.createRefreshToken(accessToken, refreshTokenExpire);

        sessionManager.updateUserSession(user.getId());

        // 记录登录信息
        HttpServletRequest request = RequestUtils.getRequest();
        String ip = RequestUtils.getClientIP(request);
        String address = RequestUtils.getClientAddress(ip);
        String deviceInfo = RequestUtils.getDeviceInfo(request);
        user.setLastLoginTime(LocalDateTime.now());
        user.setLoginCount(user.getLoginCount() + 1);
        user.setLastLoginIp(ip);
        user.setLastLoginAddress(address);
        user.setLastLoginDevice(deviceInfo);
        service.updateById(user);

        return new UserLoginOutput(accessToken, refreshToken);
    }

    /**
     * 获取登录配置
     */
    @AllowAnonymous
    @PostMapping("loginConfig")
    @ApiOperation(value = "获取登录配置", notes = "获取登录配置")
    public Result<LoginConfigOutput> getLoginConfig() {
        return Result.ok(configService.getLoginConfig());
    }

    /**
     * 获取验证码
     */
    @AllowAnonymous
    @GetMapping("captcha")
    @ApiOperation(value = "获取验证码", notes = "获取验证码")
    public Result<CaptchaOutput> captcha() {
        return Result.ok(captchaManager.generator(IdWorker.getIdStr(new Random().nextDouble())));
    }

    /**
     * 获取用户信息
     */
    @PostMapping("userInfo")
    @ApiOperation(value = "获取用户信息", notes = "获取用户信息")
    public Result<SysUserInfoOutput> getUserInfo() {
        List<String> permissions = sysMenuService.getPermissions();
        SysUserInfoOutput.SysUserInfo userInfo = BeanUtil.toBean(
                service.getById(sessionManager.getUserId()),
                SysUserInfoOutput.SysUserInfo.class);
        return Result.ok(new SysUserInfoOutput(userInfo, permissions));
    }
}
